home *** CD-ROM | disk | FTP | other *** search
/ The 640 MEG Shareware Studio 4 / The 640 Meg Shareware Studio CD-ROM Volume IV (Data Express)(1994).ISO / clang / cujaug93.zip / TWILLING.ZIP / JD2GREG.C < prev   
Text File  |  1993-02-25  |  17KB  |  376 lines

  1.  
  2. /**********************(  GREGORIAN CALENDAR MODULE )**************************
  3.  
  4.  
  5. Routines for converting between Gregorian Calendar dates and Julian Days, for
  6. validating date input, for date arithmetic, and for learning day of the week
  7. and of the year.
  8.  
  9. (c) 1991, 1993 by Bob Twilling.  
  10. C Users Journal readers may use this code for any purpose.
  11.  
  12.  
  13.  
  14. /-------------------( Export to header file "JD2GREG.H" )--------------------*/
  15.  
  16. typedef struct { short   mo;
  17.                  short   dy;
  18.                  short   yr; }  MDY;
  19.  
  20. extern MDY *  jd2greg(     long  /*jd*/,    MDY * /*date*/  );
  21. extern long   greg2jd(     short /*month*/, short /*day*/  , short /*year*/ );
  22. extern long   ValidateDate(short /*month*/, short /*day*/  , short /*year*/ );
  23. extern long   DDays(       MDY * /*date1*/, MDY * /*date2*/ );
  24. extern MDY *  DatePlus(    MDY * /*date*/ , long  /*ddays*/, MDY * /*newdt*/ );
  25. extern int    DayOfWeek(   short /*month*/, short /*day*/  , short /*year*/ ,
  26.                            char * /*name*/ );
  27. extern int    DayOfYear(   short /*month*/, short /*day*/  , short /*year*/ );
  28.  
  29.  
  30.  
  31. /*=======================(  MONTH TO DAY-OF-YEAR  )===========================/
  32.  
  33. Given a month, calc day of year up until.  This is a macro used by the next 
  34. two functions, see discussion below for the logic.  Assumes rectified years 
  35. (March is month 1).  Can change the #if to 0 to save 24 bytes of near heap 
  36. at a slight speed cost.                                                     
  37.  
  38. /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  39.  
  40. #if 1  
  41.    static short m2doy[] = 
  42.                 { 0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337 };
  43.    #define M2DOY(mo)  (m2doy[mo-1])
  44. #else
  45.    #define M2DOY(mo)  ((((mo) * 979) >> 5) - 30)
  46. #endif
  47.  
  48.  
  49.  
  50. /*=========================(  GREGORIAN TO JD  )==============================/
  51.  
  52. Given a month, day, and year, returns a long integer -- the Julian Day number
  53. at noon of that date.
  54.  
  55. ALGORITHM:
  56.  
  57. 1> Rectify the date so that the year begins on March 1. This simplifies
  58.    calculations by putting oddball leapdays last.
  59.  
  60. 2> Now calculate the day of year, e.g. Oct 28th is day number 242, Jan 3rd
  61.    is day number 309 (of the previous year! See above.)  A lookup table is
  62.    the fastest way to account for the varied number of days in a month, at
  63.    a cost of 24 bytes of near heap space.  Or we can use the formula:
  64.  
  65.          doy = ((mo * 979) / 32) - 30 + dy;
  66.  
  67.    March is the first month, remember.  There's no theory behind these magic
  68.    numbers -- a lot of other ones work too, but I like my 32 (a nice round
  69.    number).  979/32, or 367/12, or 30.6, all equal about the average days
  70.    per month, ignoring February.  The magic of integer arithmetic, and the
  71.    lucky fact that our 30-day months are well distributed, does the rest.
  72.  
  73. 3> Compute the number of days up to the beginning of the year using the
  74.    simple Gregorian formula:
  75.  
  76.          yrdays = (365 * yr) + (yr / 4) - (yr / 100) + (yr / 400);
  77.  
  78.    There are some nice round numbers in this formula too, begging to be
  79.    replaced by shifts.   
  80.  
  81. 4> Add the result of the last two steps.  Then add a constant for an origin
  82.    transform to the Julian Day system.
  83.  
  84. /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  85.  
  86. extern long greg2jd( short mo, short dy, short yr )  {
  87.  
  88.    short lp;
  89.  
  90. if ((mo -= 2) <= 0) { mo += 12; yr--; }   // 1> Roll-under the date
  91. dy += M2DOY(mo);                          // 2> Calc day of year
  92. lp = yr >> 2;                             // 3> Add and subtract leap days
  93. dy += lp;
  94. lp /= 25;
  95. dy -= lp;
  96. dy += lp >> 2;
  97. return (long)yr * 365 + dy + 1721119;  }  // 4> One div, one mul!
  98.  
  99.  
  100.  
  101. /*=========================(  JD TO GREGORIAN  )==============================/
  102.  
  103. Given a long Julian Day and a pointer to a month-day-year structure, fills in
  104. that structure with the calendar date and returns the same pointer to it.
  105.  
  106. ALGORITHM:
  107.  
  108. 1> Transform the origin of the Julian Day to the start of some 400-year
  109.    period.  Because we are assuming a JD valid in the Gregorian Calendar we
  110.    subtract 2305507, so that day 1 is 1-Mar-1600.  As the routine is currently
  111.    written this gives an algorithm valid from 1200 AD.
  112.  
  113. 2> Subtract or divide out 400-year (146097 day) blocks, then 100-year (36524
  114.    day) blocks, noting the number of blocks removed.  Since 16-bit machines
  115.    and/or compilers do long division like paint dries, we use successive
  116.    subtraction -- sort of.  Actually, we subtract too far, then add back, for
  117.    a slight speed gain.  When we migrate to 32-bits, rewrite this part for
  118.    speed and comprehensibility.
  119.  
  120. 3> Divide out 4-year (1461 day) and 1-year (365 day) blocks.  Calculate the
  121.    calendar year from the number and size of blocks removed; the remainder is
  122.    the day of the year.  If the Julian Day represented a Feb 29, the algorithm
  123.    fails here because 1461/365 equals more than four.  Check and adjust for
  124.    this special case.
  125.  
  126. 4> Calculate the month of the year from the magic-number formula:
  127.  
  128.          mo = ((doy + 30) * 32) / 979; 
  129.  
  130.    Surprisingly, this is faster on a 386 than scanning a twelve member table.
  131.    Compute the day of the month by subtracting the days up until that month
  132.    using either the table or the formula declared in M2DOY() above.
  133.  
  134. 5> Roll over the month and year if we found a date in January or February.
  135.  
  136. 6> Be careful when changing this code:  146097/36524 and 1461/365 both equal
  137.    4, which blows up the algorithm on JD's representing Feb 29's.  As written,
  138.    we avoid the first by a tricky origin shift, the second by explicitly
  139.    limiting 1461/365 to a max of three.
  140.  
  141. /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  142. #include <stdlib.h>                          //for div()
  143. /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  144.  
  145. extern MDY * jd2greg( long jd, MDY * date )  {
  146.  
  147.    unsigned short x, y, d;
  148.    div_t          sd;                       
  149.  
  150. y = 1600; jd -= 2305507;                     // 1> new origin 0 = 2/29/1600
  151. while (jd > 0)  { y += 400; jd -= 146097; }  // 2> lop off 400-yr blocks
  152. do { y -= 100; } while ((jd += 36524) < 0);  //    and add back 100-yr ones
  153. d = (unsigned short)jd;                      // 3> within 16-bit range now
  154. x = d / 1461;                                //    pity ain't no ANSI udiv()
  155. y += x << 2;                                 //    note 4-yr blocks
  156. d -= x * 1461;                               //    and lop them off
  157. sd = div( d, 365);                           //    does % and / in same op
  158. date->yr = y + sd.quot;                      //    got years, provisionally
  159. d = ++sd.rem;                                // 4> day-of-year (base 1)
  160. if (sd.quot == 4)  { date->yr--; d = 366; }  //    case we hit a leap-year
  161. x = ((d + 30) << 5) / 979;                   //    x = month, March is 1
  162. date->dy = d - M2DOY(x);                     //    got day of month
  163. if ((x += 2) > 12)  { date->yr++; x -= 12; } // 5> roll around Jan and Feb 
  164. date->mo = x;                                //    got month
  165. return(date);  }
  166.  
  167.  
  168.  
  169. /*==========================(  VALIDATE DATE  )===============================/
  170.  
  171. Given a month, day, and year, returns a positive long integer representing the
  172. corresponding Julian Day at 12h UTC, unless the passed date is invalid in the
  173. Gregorian Calendar, in which case returns zero.  This somewhat-boolean retval
  174. will save the user another call to greg2jd() if the date does prove valid.
  175.  
  176. ALGORITHM:
  177.  
  178. 1> Convert the passed Gregorian date into its Julian Day.
  179.  
  180. 2> Check that the date is not earlier than 15 Oct 1582, the first day
  181.    Gregory's calendar was in use anywhere.  An earlier date would indicate
  182.    that the caller should have used Julian Calendar (Old Style) conversion
  183.    routines.
  184.  
  185. 3> Check that the date is not later than 28 Feb 4000.  On somewhat shaky
  186.    ground here.  If our planet's orbital speed doesn't change much, Greg's
  187.    calendar will lose a day every 3300 years.  I read somewhere of a proposal
  188.    for a Revised Gregorian Calendar which drops the leap year in 4000 AD and
  189.    every four thousand years thereafter.  But I don't know if the proposal has
  190.    been widely accepted, don't know what's going to happen culturally or
  191.    astronomically over two millennia, and don't really care since this
  192.    module's goal is to implement only Gregory's original system.
  193.  
  194.    Just to be safe, I have provisionally and unilaterally declared 4000 AD 
  195.    to be the end of the Gregorian Calendar.  When the time comes, make sure
  196.    either to remove this restriction or to rewrite the formulae.
  197.  
  198. 4> If the Julian Day passes the boundary conditions, convert it back to a
  199.    calendar date and check that that date equals the caller's.  This shows up
  200.    mistakes like 31 Nov 1991, or 3.15.1992 (when the caller was expecting
  201.    Canadian-style input).
  202.  
  203. /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  204.  
  205. extern long ValidateDate( short mo, short dy, short yr)  {
  206.  
  207. MDY  date;
  208. long jd = greg2jd( mo, dy, yr);
  209. if ((jd >= 2299161) && (jd <= 3182088))  { 
  210.    jd2greg(jd, &date);
  211.    if (date.dy == dy && date.mo == mo && date.yr == yr)  {
  212.       return jd;  }  }                    //suceeded
  213. return 0;  }                              //failed
  214.  
  215.  
  216.  
  217. /*=========================(  DIFFERENCE IN DAYS  )============================
  218.  
  219. Given two pointers to month-day-year structures, returns the positive or
  220. negative difference between date2 and date1.  Value is positive if the second
  221. date is later than the first, in keeping with the HP-41 function even though
  222. this seems to violate RPN logic.  No date validation is done, and the
  223. subtraction could fail for dates outside the original Gregorian range.  Could
  224. easily inline this function.
  225.  
  226. /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  227.  
  228. extern long DDays( MDY * date1, MDY * date2)  {
  229.  
  230. return greg2jd(date2->mo, date2->dy, date2->yr) 
  231.        - greg2jd(date1->mo, date1->dy, date1->yr);  }
  232.  
  233.  
  234.  
  235. /*===========================(  DATE ADDITION  )===============================
  236.  
  237. Given two pointers to month-day-year structures and a (positive or negative)
  238. long integer, adds that integer number of days to the first MDY and fills in
  239. the second with the answer.  Returns the pointer to the second structure.
  240. Another good candidate for inlining.
  241.  
  242. /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  243.  
  244. extern MDY * DatePlus( MDY * date, long dys, MDY * newdate)  {
  245.  
  246. return jd2greg((greg2jd( date->mo, date->dy, date->yr) + dys), newdate );  }
  247.  
  248.  
  249.  
  250. /*============================(  DAY OF WEEK  )===============================/
  251.  
  252. Given month-day-year, returns an integer representing the day of the week.
  253. Zero is Monday.  Caller can also get the weekday name by passing a pointer to
  254. a string workspace at least 7 characters long.  Most such callers will want to
  255. either abbreviate the string to 3 characters, or add a "day" onto it using
  256. strcat() or as part of a printf() format.  Callers that do not want the string
  257. must pass a NULL pointer.  Algorithm is simply (JD mod 7).
  258.  
  259. /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  260. #include <string.h>                             //for strcpy()
  261. /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  262.  
  263. int DayOfWeek( short mo, short dy, short yr, char * strg)  {
  264.  
  265. static char * dnm[] = { "Mon","Tues","Wednes","Thurs","Fri","Satur","Sun" };
  266. int d = (short)(greg2jd(mo,dy,yr) % 7);
  267. if (strg)  strcpy(strg, dnm[d]);
  268. return d;  }
  269.  
  270.  
  271.  
  272. /*============================(  DAY OF YEAR  )===============================/
  273.  
  274. Given a month-day-year, returns the day of that year.  Jan first is 1.  Handy
  275. for numbering invoices, etc.  An invalid Gregorian date returns 0.  The
  276. routine is correct for Gregory's short year 1582, although we could probably
  277. save the code -- the result will usually be meaningless for historical
  278. periods, when the year began in March.  Algorithm is simple JD subtraction.
  279.  
  280. /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  281.  
  282. int DayOfYear( short mo, short dy, short yr)  {
  283.  
  284. long jd = ValidateDate( mo, dy, yr);
  285. if (jd)  {
  286.    if (yr != 1582)  {
  287.       return (int)(jd - greg2jd( 1, 1, yr)) + 1;  }
  288.    else  {                    //10 days missing from 1582
  289.       return (int)(jd - 2299161 + 278);  }  }
  290. else return 0;  }
  291.  
  292.  
  293.  
  294. /*=============================(  CAUTIONS  )=================================/
  295.  
  296. 1> All callers, be aware that some of these routines work only when passed a
  297.    valid Julian Day.  As a matter of course, validate your calendar dates.
  298.  
  299. 2> Astronomy callers, be aware....
  300.  
  301.    The Julian Day passed to and from these routines is at 12h UTC.  So
  302.    ephemerides should use this formula to calculate the proper fractional
  303.    Julian Day.:
  304.          JD = (double)greg2jd() - 0.5 + (UTC / 24.0); 
  305.  
  306. 3> Non-astronomy users, you can safely assume that Local Time equals UTC only
  307.    so long as you are not crossing different time zones.
  308.  
  309. 4> Geneology and History callers, be aware for two reasons....
  310.  
  311.    High school history may have taught us that Europeans changed to the
  312.    Gregorian Calendar on 15 Oct 1582.  'Taint so.  Only the Papal States
  313.    changed on that date.  Even other Catholic countries took a up to a couple
  314.    years to switch, after putting down their rent riots.  (What tenant farmer
  315.    wants to pay full price for a 20 day month?)  And, on the principle that a
  316.    good idea from my enemy is a bad idea, Protestant countries took a couple
  317.    hundred years to change over, Orthodox ones even longer.  Washington was
  318.    born on 11 Feb, he thought, until England switched in 1752 to give us our
  319.    Feb 22 holiday.  Russia didn't switch until the Revolution (the 1917, not
  320.    this one).
  321.  
  322.    Reason two:  New Years Day wasn't always the first of January.  Again,
  323.    Christendom switched from a date, usually in March, to Jan 1 at various,
  324.    seemingly whimsical, times.
  325.  
  326.    The upshot?  That date -- in the back of an old Bible, on the Margraves'
  327.    and Metropolitans' proclamations -- may or may not be in Gregory's system.
  328.    You'll need to know the calendar used during your period of study better
  329.    than this program does.
  330.  
  331.  
  332.  
  333. /===============================(  HISTORY  )=================================/
  334.  
  335. Feb 1582:    Pope Gregory XIII, acting on recommendation of a scientific
  336.              commission, reforms Julius Caesar's calendar to drop leap days
  337.              on years divisible by 100 unless also divisible by 400.  He
  338.              decrees next October will drop 10 days to eliminate accumulated
  339.              error so that Easter gets back on track.
  340.  
  341. 1582:        Joe Scaliger, perhaps inspired by the Mayans, invents a "long
  342.              count" calendar as a standard of comparison between different
  343.              chronological systems.  His epoch begins 4713 BC and lasts 7980
  344.              years.  He writes routines for both the Julian and Gregorian
  345.              calendars.  He names his epoch "Julian Days" after his father-in-
  346.              law (or father or uncle, sources vary), to the eternal confusion
  347.              of students' who imagine it has something to do with the Caesar.
  348.              My sources don't say whether Joe is a member of the papal
  349.              commission or whether it is news reports of their findings that
  350.              inspires him to program his new system.
  351.  
  352. 1898:        Sam Newcomb publishes Julian Day formulae for the "American
  353.              Ephemeris" and "Nautical Almanac."  Astronomers are still using
  354.              Joe's epoch because it handily predates all recorded (even
  355.              Chinese and African) observations.
  356.  
  357. 1979:        Jean Meeus publishes "Astronomical Formulae for Calculators" in
  358.              Belgium.  A French edition follows in 1980, an American one in
  359.              1985.  The calendar routines in his books are adopted by many
  360.              programmers to replace previous buggy ones.  I adopt them in
  361.              1989.  Still, after 400 years, Julian Days are the basis for
  362.              time calculation.  Nice job, Joe.
  363.  
  364. Aug 1991:    I write a program in which Meeus' calendar routines are the only
  365.              ones using floating-point math.  And they are pulling in a whole
  366.              16K emulation library.  I rewrite to use long integers instead of
  367.              floats.  And I drop Julian Calendar conversions -- they will be
  368.              in a separate module should I ever need them.
  369.  
  370. 9 Feb 1993:  I squeeze a little more speed from the integer routines, add
  371.              excessive comments, and add a 4000 AD validation check.
  372.  
  373.  
  374. /===================================================================ENDIT====*/
  375.  
  376.